package protocol.arq.gobackN;
import java.io.BufferedWriter;
import java.io.IOException;

import protocol.arq.ARQ;
import protocol.arq.ARQReceiver;
import protocol.hdlc.HDLCProtocol;
import utils.advancedSocket.AdvancedSocket;
import utils.trame.MalformedTrameException;
import utils.trame.Trame;
import utils.trame.Trame.TrameType;

/**
 * Implemente le protocole de reception "Go-Back-N".
 * 
 * @see GoBackNSender
 * @author Bertholet Damien & Daoust Julien
 */
public class GoBackNReceiver extends ARQReceiver {

	/**
	 * Dernier numero de trame correctement recu. Sera -1 si aucune trame n'a
	 * ete correctement recu.
	 */
	private int lastNumReceived = -1;

	/**
	 * Dernier trame envoye. Sera <code>null</code> si aucune trame n'a ete
	 * envoye.
	 */
	private Trame lastTrameSend = null;

	/**
	 * Constructeur.
	 * 
	 * @param advancedSocket
	 *            L'{@link AdvancedSocket}.
	 * @param writer
	 *            Un {@link BufferedWriter} pour ecrire les donneees recus.
	 */
	public GoBackNReceiver(AdvancedSocket advancedSocket, BufferedWriter writer) {
		super(advancedSocket, writer);

		sendTrame(new Trame(TrameType.A, 0));
	}

	/*
	 * (non-Javadoc)
	 * @see protocol.arq.ARQ#receiving(java.lang.String)
	 */
	@Override
	protected void receivedTrame(String data) {
		Trame trame = null;

		if(HDLCProtocol.VERBOSE)
			System.out.println("Receive :" + data);

		try {
			trame = ARQ.decodeTrame(data);
		} catch (MalformedTrameException exception) {
			if(HDLCProtocol.VERBOSE)
				System.out.println("Malformed Trame\n");

			sendREJ();
			return;
		}

		if(HDLCProtocol.VERBOSE)
			System.out.println(trame.toExpliciteString() + "\n");

		switch(trame.getType()) {
		case I:
			// Le numero de trame qu'on devrait recevoir.
			int numHope = (lastNumReceived + 1) % windowSize;

			// Si on n'a pas la trame que l'on attend, on envoie un rej car sa
			// signifie qu'on a pas recu la trame souhaite.
			if(numHope != trame.getNum()) {
				sendREJ(trame.getNum());
				return;
			}

			receivedGoodTrame(trame);

			break;
		case P:
			sendRR();
			break;
		case F:
			closeConnection();
			break;
		case A:
		case R:
		case C:
		default:
			// On ne fait rien.
			break;
		}
	}

	/**
	 * On recoit une {@link Trame} bien forme qui est la suite des donnees deja
	 * recu.
	 * 
	 * @param trame
	 *            La {@link Trame} recu.
	 */
	private void receivedGoodTrame(Trame trame) {
		try {
			writer.write(trame.getData() + "\n");
		} catch (IOException e) {
			// Erreur d'ecriture
			e.printStackTrace();
		}

		lastNumReceived = trame.getNum();

		// On envoie un acquittement seulement quand on est au numero 0 ou a la
		// moitie de la fenetre.
		if(lastNumReceived == 0 || lastNumReceived == windowSize / 2)
			sendRR();
		else
			lastTrameSend = null;
	}

	/**
	 * Envoie une {@link Trame} de type A dependamment du dernier index recu.
	 */
	private void sendRR() {
		int nextNumHope = getIndexWindow(lastNumReceived + 1);
		lastTrameSend = new Trame(TrameType.A, nextNumHope);
		sendTrame(lastTrameSend);
	}

	/**
	 * Envoie une {@link Trame} de type R dependamment de l'etat actuel si
	 * besoin.
	 * 
	 * @param num
	 *            Le numero de la {@link Trame} qui a ete envoye a la place de
	 *            celle attendue.
	 */
	private void sendREJ(int num) {
		int nextNumHope = getIndexWindow(lastNumReceived + 1);

		// Rej deja envoye et le 'num' ne correspond pas a la trame suivante.
		// On evite ainsi de renvoyer trop de fois le REJ. On ne l'envoie que le
		// nombre de fois utile.
		if(lastTrameSend != null && lastTrameSend.getType() == TrameType.R &&
		        lastTrameSend.getNum() == nextNumHope)
			return;

		sendREJ();
	}

	/**
	 * Envoie une {@link Trame} de type R dependamment de l'etat actuel.
	 */
	private void sendREJ() {
		int nextNumHope = getIndexWindow(lastNumReceived + 1);

		lastTrameSend = new Trame(TrameType.R, nextNumHope);
		sendTrame(lastTrameSend);
	}

	/*
	 * (non-Javadoc)
	 * @see protocol.arq.ARQ#method()
	 */
	@Override
	protected int getMethod() {
		return 0;
	}
}